package com.pinfly.common.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.apache.log4j.Logger;

import com.pinfly.common.crypto.AESEncryption;
import com.pinfly.common.crypto.CryptoException;
import com.pinfly.common.crypto.CryptoFactory;
import com.pinfly.common.crypto.ICrypto;
import com.pinfly.common.util.StringUtil;

public class PfyPropertiesImpl implements IPfyProperties
{
    private static final Logger logger = Logger.getLogger (PfyPropertiesImpl.class);

    private static ICrypto s_crypto;
    // These groups use the default keys, need to bootstrap using encrypted
    // value
    // to read the keystore group.
    private static Set <String> s_specialGroups = new HashSet <String> ();
    private static AESEncryption s_specialCrypto = null;

    static
    {
        try
        {
            s_specialGroups.add ("cryptokeys");
            s_specialCrypto = new AESEncryption ();

            s_crypto = (ICrypto) CryptoFactory.getInstance ();
        }
        catch (CryptoException ce)
        {
            logger.warn ("Unable to retrieve a Crypto implementation", ce);
        }
    }

    // Private methods

    /**
     * Returns a valid path for the specified group
     * 
     * @param group
     * @return filePath
     * @throws ConfigException
     */
    private String getValidPropertiesFilepath (String group) throws ConfigException
    {
        return makeFullPath (BaseConfigUtil.getPfyPropertiesDir (), group);
    }

    /**
     * Returns the fully qualified property file name.
     * 
     * @param dirPath
     * @param group
     * @return the fully qualified property file name.
     * @throws ConfigException
     */
    private String makeFullPath (String dirPath, String group) throws ConfigException
    {
        StringBuilder fullPath = new StringBuilder ();
        fullPath.append (dirPath);
        fullPath.append (File.separator);
        fullPath.append (group);
        fullPath.append (BaseConfigConstants.PROPERTIES_EXT);

        File f = new File (fullPath.toString ());
        if (f.exists ())
        {
            return fullPath.toString ();
        }
        else
        {
            throw new ConfigException ("Properties file doesn't exist at " + fullPath.toString ());
        }
    }

    /**
     * Returns the CxxProperties for the group using the specified filepath.
     * 
     * @param filepath
     * @param groupName
     * @return CxxProperties
     * @throws ConfigException
     * @throws IllegalArgumentException if filepath or groupName is null or
     *             empty
     */
    private PfyProperties getPropertiesUsingFile (String filepath, String groupName) throws ConfigException
    {
        if (StringUtil.isNullOrBlank (filepath))
        {
            throw new IllegalArgumentException ("filepath can't be null or empty string");
        }
        if (StringUtil.isNullOrBlank (groupName))
        {
            throw new IllegalArgumentException ("groupName can't be null or empty string");
        }

        FileInputStream input = null;
        Properties prop = null;

        try
        {
            File f = new File (filepath);
            input = new FileInputStream (f);
            prop = new Properties ();
            prop.load (input);

            PfyProperties cfgProp = new PfyProperties (groupName);

            for (Entry <Object, Object> entry : prop.entrySet ())
            {
                String key = (String) entry.getKey ();
                String value = (String) entry.getValue ();

                if (value != null && value.length () > 0)
                {
                    if (s_specialGroups.contains (groupName.toLowerCase ())
                        && value.startsWith (BaseConfigConstants.ENCRYPTED_KEY))
                    {
                        value = value.trim ();

                        // Decrypt the value
                        if (s_specialCrypto != null)
                        {
                            try
                            {
                                value = s_specialCrypto.decrypt (value.substring (BaseConfigConstants.ENCRYPTED_KEY.length ()));
                            }
                            catch (CryptoException ce)
                            {
                                logger.warn ("Unable to decrypt value (" + value + ") in " + groupName
                                             + " just returning the original value.", ce);
                            }
                        }
                    }
                    else if (value.startsWith (BaseConfigConstants.ENCRYPTED_KEY))
                    {
                        value = value.trim ();

                        // Decrypt the value
                        try
                        {
                            // Replace with the decrypted value
                            value = s_crypto.decrypt (value.substring (BaseConfigConstants.ENCRYPTED_KEY.length ()));
                        }
                        catch (CryptoException ce)
                        {
                            logger.warn ("Unable to decrypt value (" + value + ") in " + groupName
                                         + " just returning the original value.", ce);
                        }
                    }
                }

                cfgProp.put (key, value);
            }

            return cfgProp;
        }
        catch (FileNotFoundException fe)
        {
            throw new ConfigException (
                                       "Unable to find the properties for groupName=" + groupName + " file=" + filepath,
                                       fe);
        }
        catch (IOException ie)
        {
            throw new ConfigException (
                                       "Unable to read the properties for groupName=" + groupName + " file=" + filepath,
                                       ie);
        }
        finally
        {
            if (input != null)
            {
                try
                {
                    input.close ();
                }
                catch (Exception e)
                {
                }
            }
        }
    }

    // Public methods

    public PfyProperties getProperties (String groupName) throws ConfigException
    {
        if (StringUtil.isNullOrBlank (groupName))
        {
            throw new IllegalArgumentException ("groupName can't be null or empty string");
        }

        String filepath = getValidPropertiesFilepath (groupName);
        PfyProperties prop = getPropertiesUsingFile (filepath, groupName);
        return prop;
    }

    public PfyProperties getProperties (String filePath, String groupName) throws ConfigException
    {
        if (StringUtil.isNullOrBlank (filePath))
        {
            throw new IllegalArgumentException ("filePath can't be null or empty string");
        }

        if (StringUtil.isNullOrBlank (groupName))
        {
            throw new IllegalArgumentException ("groupName can't be null or empty string");
        }

        PfyProperties prop = getPropertiesUsingFile (makeFullPath (filePath, groupName), groupName);
        return prop;
    }

    public Collection <PfyProperties> getProperties (Collection <String> groupNames) throws ConfigException
    {
        if (groupNames == null || groupNames.size () == 0)
        {
            throw new IllegalArgumentException ("groupNames can't be null or empty");
        }

        Collection <PfyProperties> props = new ArrayList <PfyProperties> ();
        for (String group : groupNames)
        {
            String filepath = getValidPropertiesFilepath (group);
            PfyProperties prop = getPropertiesUsingFile (filepath, group);

            props.add (prop);
        }

        return props;
    }

    public Collection <PfyProperties> getProperties (String filePath, Collection <String> groupNames)
                                                                                                     throws ConfigException
    {
        if (StringUtil.isNullOrBlank (filePath))
        {
            throw new IllegalArgumentException ("filePath can't be null or empty string");
        }

        if (groupNames == null || groupNames.size () == 0)
        {
            throw new IllegalArgumentException ("groupNames can't be null or empty");
        }

        Collection <PfyProperties> props = new ArrayList <PfyProperties> ();
        for (String group : groupNames)
        {
            PfyProperties prop = getPropertiesUsingFile (makeFullPath (filePath, group), group);

            props.add (prop);
        }

        return props;
    }

    public GroupMap getPropertiesMap (Collection <String> groupNames) throws ConfigException
    {
        if (groupNames == null || groupNames.size () == 0)
        {
            throw new IllegalArgumentException ("groupNames can't be null or empty");
        }

        Collection <String> newGroupNames = FileSystemUtil.updateGroupNameWithWildCard (groupNames,
                                                                                        BaseConfigConstants.PROPERTIES_EXT);

        GroupMap map = new GroupMap ();
        for (String group : newGroupNames)
        {
            String filepath = getValidPropertiesFilepath (group);
            PfyProperties prop = getPropertiesUsingFile (filepath, group);

            map.setProperties (group, prop);
        }

        return map;
    }

    public GroupMap getPropertiesMap (String filePath, Collection <String> groupNames) throws ConfigException
    {
        if (StringUtil.isNullOrBlank (filePath))
        {
            throw new IllegalArgumentException ("filePath can't be null or empty string");
        }

        if (groupNames == null || groupNames.size () == 0)
        {
            throw new IllegalArgumentException ("groupNames can't be null or empty");
        }

        Collection <String> newGroupNames = FileSystemUtil.updateGroupNameWithWildCard (filePath,
                                                                                        groupNames,
                                                                                        BaseConfigConstants.PROPERTIES_EXT);

        GroupMap map = new GroupMap ();
        for (String group : newGroupNames)
        {
            PfyProperties prop = getPropertiesUsingFile (makeFullPath (filePath, group), group);

            map.setProperties (group, prop);
        }

        return map;
    }

}
